﻿// Copyright (c) Microsoft Corporation.  All rights reserved. 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WiE.Model;
using WiE.SitkaSoapService;
using System.ServiceModel;

namespace WiE.Model
{
    /// <summary>
    /// Data model for the WIEViewer application that wraps the SSDS data service.  Note that we don't reuse the mobile client's implementation
    /// as it does not leverage WCF, hopefully at some point we will be able to merge the two implementations.
    /// </summary>
    class WiERemoteModelSSDS : IWiEModel
    {
        // Sitka Credentials
        // todo update the login credentials for your SQL Data Services Account
#warning REMEMBER to update WiERemodeSSDS.cs to have your SQL Data Services credentials.
        private const string LOGIN_NAME = "YOUR SDS LOGIN NAME HERE";
        private const string LOGIN_PASSWORD = "YOUR SDS PASSWORD HERE";

        // SSDS Authority.  In this implementation we use only one authority.  In a global deployment we may want
        // to implement an authority per regional community (friends are most likely to be in the same region)
        private const string AUTHORITY_NAME = "wie";

        // SSDS Container.  In this implementation we only have one container, but as the application is scaled we 
        // might want to distribute and partition the data across multiple containers.
        private const string CONTAINER_NAME = "wie";

        // SSDS Entity Types
        private const string LOCATION_KIND = "location";
        private const string DEVICE_KIND = "device";
        private const string MEMBER_KIND = "member";

        // SOAP Web Service wrapper / proxy to Sitka SSDS Data service
        //
        // todo: wrap the web service proxy with a class to abstract away the differences between web service proxy
        // on mobile client and web / wpf apps that support WCF
        SitkaSoapServiceClient m_sitkaSoapService = null;

        /// <summary>
        /// Event raised when the location history is changed (items added, removed or updated via the model)
        /// </summary>
        public event EventHandler<LocationHistoryChangedArgs> RaiseLocationHistoryChangedEvent;

        /// <summary>
        /// Event raised when the devices are changed (items added, removed or updated via the model)
        /// </summary>
        public event EventHandler<DeviceChangedArgs> RaiseDeviceChangedEvent;

        /// <summary>
        /// Event raised when a member is changed (added, removed or updated via the model)
        /// </summary>
        public event EventHandler<MemberChangedArgs> RaiseMemberChangedEvent;

        public void Initialize()
        {
            // Initialize the web service proxy
            m_sitkaSoapService = new SitkaSoapServiceClient("BasicAuthEndpoint");
            m_sitkaSoapService.ClientCredentials.UserName.UserName = LOGIN_NAME;
            m_sitkaSoapService.ClientCredentials.UserName.Password = LOGIN_PASSWORD;
        }

        public void Terminate()
        {
            // Clean up anything we would like cleaned up...
        }

        /// <summary>
        /// Returns the availability of the underlying SSDS data store.
        /// </summary>
        /// <returns></returns>
        public bool IsAvailable()
        {
            // todo: implement checking for internet connectivity.
            return true;
        }

        #region QueryAndEntityHelperMethods
                
        /// <summary>
        /// Helper method that generates an SSDS Entity based on a WiE DataObject derived class by converting the
        /// DataObject into a property bag and then populating the entity's property array based on the DataObject's property bag.
        /// </summary>
        /// <param name="p_dataObject"></param>
        /// <returns></returns>
        private Entity DataObjectToSSDSEntity(WiEDataObject p_dataObject)
        {
            // Retrieve the data object in a property bag form
            Dictionary<string, object> dataObjectProperties = p_dataObject.ToPropertyDictionary();

            // Create a new entity object and initialize it
            Entity entity = new Entity();

            // Now create properties on the Entity to contain the values from the Data Object properties
            entity.Properties = dataObjectProperties;

            // Return the entity with populate properties... still needs to have its Type and ID set.
            return entity;
        }

        /// <summary>
        /// Returns a "generic" list of data objects matching the specified query.  This method supports returning a 
        /// result set made up of objects of different types based on WiEDataObject.  This may be used to perform
        /// some client side "joins" between data objects.
        /// </summary>
        /// <param name="p_strQuery"></param>
        /// <returns></returns>
        private List<WiEDataObject> GetQueryResults(string p_strQuery)
        {
            List<Entity> entities = null;
            List<WiEDataObject> queryResults = new List<WiEDataObject>();

            Scope containerScope = new Scope();
            containerScope.AuthorityId = AUTHORITY_NAME;
            containerScope.ContainerId = CONTAINER_NAME;

            try
            {
                // try to query SSDS for the member
                entities = m_sitkaSoapService.Query(containerScope, p_strQuery);

                foreach (Entity currentEntity in entities)
                {
                    switch (currentEntity.Kind)
                    {
                        case LOCATION_KIND:
                            WiELocationDataObject newLocationDataObject = new WiELocationDataObject(currentEntity.Properties);
                            queryResults.Add(newLocationDataObject);
                            break;
                        case MEMBER_KIND:
                            WiEMemberDataObject newMemberDataObject = new WiEMemberDataObject(currentEntity.Properties);
                            queryResults.Add(newMemberDataObject);
                            break;
                        case DEVICE_KIND:
                            WiEDeviceDataObject newDeviceDataObject = new WiEDeviceDataObject(currentEntity.Properties);
                            break;
                        default:
                            System.Diagnostics.Trace.WriteLine("Unsupported or deprecated entity was returned: {0}.", currentEntity.Kind);
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                // There was a problem querying for location history.
                System.Diagnostics.Trace.WriteLine("There was a problem querying data object(s): " + ex.ToString());
            }

            return queryResults;
        }
        

        /// <summary>
        /// Helper function that saves a populated entity to the authority and container within SSDS by calling the web service proxy.
        /// </summary>
        /// <param name="p_entity"></param>
        /// <returns></returns>
        private bool SaveEntity(Entity p_entity)
        {
            bool bReturnValue = true;

            Scope locationContainerScope = new Scope();
            locationContainerScope.AuthorityId = AUTHORITY_NAME;
            locationContainerScope.ContainerId = CONTAINER_NAME;

            try
            {
                // Call the web service to create the entity
                m_sitkaSoapService.Create(locationContainerScope, p_entity);
            }
            catch (FaultException<Error> createError)
            {
                // If the error was that the entity already exists, we should simply do an update
                if (createError.Detail.StatusCode == ErrorCodes.EntityExists)
                {
                    Console.WriteLine("Entity {0} already exists, attempting update.", p_entity.Id);
                  
                    try
                    {
                        // For now Assume this was a fault because entity already exists...
                        m_sitkaSoapService.Update(locationContainerScope, p_entity);
                    }
                    catch (FaultException<Error> updateError)
                    {
                        bReturnValue = false;
                        Console.WriteLine("Error: {0}:{1}", updateError.Detail.StatusCode, updateError.Detail.Message);
                    }
                }
                else
                {
                    bReturnValue = false;
                    Console.WriteLine("Error: {0}:{1}", createError.Detail.StatusCode, createError.Detail.Message);
                }
            }
            catch (Exception ex)
            {
                // There was an issue trying to create the authority, it may already exist.
                bReturnValue = false;
                Console.WriteLine("There was an issue saving the entity: " + ex.ToString());
            }
  
            return bReturnValue;
        }

        #endregion

        #region AuthorityAndContainerHelperMethods

        /// <summary>
        /// Helper function to create the Authority if it does not already exist for the WiE Service
        /// </summary>
        /// <returns>true if successful</returns>
        private bool InitializeAuthority()
        {
            bool bReturnValue = true;

            Scope serviceScope = new Scope();

            Authority authority = new Authority();

            authority.Id = AUTHORITY_NAME;

            try
            {
                // Call the web service to create the authority
                m_sitkaSoapService.Create(serviceScope, authority);
            }
            catch (FaultException<Error> createAuthorityError)
            {
                if (createAuthorityError.Detail.StatusCode != ErrorCodes.EntityExists)
                {
                    bReturnValue = false;
                    Console.WriteLine("Error: {0}:{1}", createAuthorityError.Detail.StatusCode, createAuthorityError.Detail.Message);
                }
            }
            catch (Exception ex)
            {
                // There was an issue trying to create the authority, it may already exist.
                bReturnValue = false;
                Console.WriteLine("There was an issue creating / initializing Authority: " + ex.ToString());
            }

            return bReturnValue;
        }

        /// <summary>
        /// Helper function to initialize the Where is Ed container within the where is ed authority.
        /// </summary>
        /// <returns></returns>
        private bool InitializeContainer()
        {
            bool bReturnValue = true;

            Scope serviceScope = new Scope();
            serviceScope.AuthorityId = AUTHORITY_NAME;

            Container container = new Container();
            container.Id = CONTAINER_NAME;

            try
            {
                // Call the web service to create the authority
                m_sitkaSoapService.Create(serviceScope, container);
            }
            catch (FaultException<Error> createContainerError)
            {
                if (createContainerError.Detail.StatusCode != ErrorCodes.EntityExists)
                {
                    bReturnValue = false;
                    Console.WriteLine("Error: {0}:{1}", createContainerError.Detail.StatusCode, createContainerError.Detail.Message);
                }
            }
            catch (Exception ex)
            {
                // There was an issue trying to create the authority, it may already exist.
                bReturnValue = false;
                Console.WriteLine("There was an issue creating / initializing Container: " + ex.ToString());
            }

            return (bReturnValue);
        }

        #endregion

        /// <summary>
        /// Helper function for all the GetLocationHistoryByXXXX methods
        /// </summary>
        /// <param name="p_queryString"></param>
        /// <returns></returns>
        private List<WiELocationDataObject> QueryForLocationHistory(string p_queryString, Comparison<WiELocationDataObject> p_comparisonFunction)
        {
            List<WiELocationDataObject> locationHistory = null;
            List<Entity> entities = null;

            Scope containerScope = new Scope();
            containerScope.AuthorityId = AUTHORITY_NAME;
            containerScope.ContainerId = CONTAINER_NAME;

            try
            {
                entities = m_sitkaSoapService.Query(containerScope, p_queryString);
            }
            catch(Exception ex)
            {
                // There was a problem querying for location history.
                Console.WriteLine("There was a problem querying for the location history: " + ex.ToString());
            }

            if ((entities != null) && (entities.Count > 0))
            {
                locationHistory = new List<WiELocationDataObject>();

                // Turn all the generic entities into WiELocationDataObjects
                foreach (Entity currentEntity in entities)
                {
                    WiELocationDataObject locationObject = new WiELocationDataObject(currentEntity.Properties);
                    locationHistory.Add(locationObject);
                }

                if (p_comparisonFunction != null)
                {
                    // Sort the resulting list 
                    locationHistory.Sort(p_comparisonFunction);
                }
            }

            return (locationHistory);
        }

        /// <summary>
        /// Return list of WiEMemberDataObjects that match the p_strQuery
        /// </summary>
        /// <param name="p_strQuery"></param>
        /// <param name="p_comparisonFunction"></param>
        /// <returns></returns>
        private List<WiEMemberDataObject> QueryForMembers(string p_strQuery, Comparison<WiEMemberDataObject> p_comparisonFunction)
        {
            List<WiEMemberDataObject> members = new List<WiEMemberDataObject>(); ;

            // Query for all the matching member data objects
            List<WiEDataObject> memberDataObjects = GetQueryResults(p_strQuery);

            // Did we get any matches?
            if (memberDataObjects.Count>0)
            {
                // Turn all the generic entities into WiELocationDataObjects
                foreach (WiEDataObject currentDataObject in memberDataObjects)
                {
                    members.Add((WiEMemberDataObject) currentDataObject);
                }

                // Sort the list if a sort function was specified.
                if (p_comparisonFunction != null)
                {
                    // Sort the resulting list 
                    members.Sort(p_comparisonFunction);
                }
            }

            return (members);
        }

        #region IWiEModel Members

        /// <summary>
        /// Returns the entire location history for the specified member (this may not be a good idea).
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryForMember(Guid p_guidMemberID)
        {
            List<WiELocationDataObject> locationHistory = null;

            // Build the querystring
            string queryString = @"from e in entities where (e.Kind == """ + LOCATION_KIND + @""") && (e[""" +
                                        WiELocationDataObject.KEY_MEMBERID + @"""] == """ + p_guidMemberID.ToString() + @""") select e";

            // call the helper method to query and sort the results
            locationHistory = QueryForLocationHistory(queryString, WiELocationDataObject.CompareDateCollectedForDescendingOrder);

            // return the results
            return (locationHistory);
        }

        /// <summary>
        /// Returns the location history for the specified member in descending order by datetime collected (most recent first).
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        /// <param name="p_dtStartTimeUTC"></param>
        /// <param name="p_dtStopTimeUTC"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryForMemberBetween(Guid p_guidMemberID, DateTime p_dtStartTimeUTC, DateTime p_dtStopTimeUTC)
        {
            List<WiELocationDataObject> locationHistory = null;

            // Build the querystring
            string queryString = @"from e in entities where (e.Kind == """ + LOCATION_KIND + @""") && (e[""" + 
                                        WiELocationDataObject.KEY_DATECOLLECTED + @"""] >= DateTime(""" + p_dtStartTimeUTC.ToString() + @""")) && (e[""" +
                                        WiELocationDataObject.KEY_DATECOLLECTED + @"""] <= DateTime(""" + p_dtStopTimeUTC.ToString() + @""")) && (e[""" +
                                        WiELocationDataObject.KEY_MEMBERID + @"""] == """ + p_guidMemberID.ToString() + @""") select e";

            // call the helper method to query and sort the results
            locationHistory = QueryForLocationHistory(queryString, WiELocationDataObject.CompareDateCollectedForDescendingOrder);

            // return the results
            return (locationHistory);
        }

        /// <summary>
        /// Returns the location history for the specified member for locations captured before a certain time (typically used for synchronization purposes).
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        /// <param name="p_dtEndTimeUTC"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryForMemberBefore(Guid p_guidMemberID, DateTime p_dtEndTimeUTC)
        {
            List<WiELocationDataObject> locationHistory = null;

            // Build the querystring
            string queryString = @"from e in entities where (e.Kind == """ + LOCATION_KIND + @""") && (e[""" +
                                        WiELocationDataObject.KEY_DATECOLLECTED + @"""] <= DateTime(""" + p_dtEndTimeUTC.ToString() + @""")) && (e[""" +
                                        WiELocationDataObject.KEY_MEMBERID + @"""] == """ + p_guidMemberID.ToString() + @""") select e";

            // call the helper method to query and sort the results
            locationHistory = QueryForLocationHistory(queryString, WiELocationDataObject.CompareDateCollectedForDescendingOrder);

            // return the results
            return (locationHistory);
        }

        /// <summary>
        /// Returns the entire location history for the specified device (this may not be a good idea).
        /// </summary>
        /// <param name="p_guidDeviceID"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryForDevice(Guid p_guidDeviceID)
        {
            List<WiELocationDataObject> locationHistory = null;

            // Build the querystring
            string queryString = @"from e in entities where (e.Kind == """ + LOCATION_KIND + @""") && (e[""" +
                                        WiELocationDataObject.KEY_DEVICEID + @"""] == """ + p_guidDeviceID.ToString() + @""") select e";

            // call the helper method to query and sort the results
            locationHistory = QueryForLocationHistory(queryString, WiELocationDataObject.CompareDateCollectedForDescendingOrder);

            // return the results
            return (locationHistory);
        }

        /// <summary>
        /// Returns list of location data objects for the specified device for the period p_dtStartTimeUTC to p_dtStopTimeUTC.
        /// </summary>
        /// <param name="p_guidDeviceID"></param>
        /// <param name="p_dtStartTimeUTC"></param>
        /// <param name="p_dtStopTimeUTC"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryForDeviceBetween(Guid p_guidDeviceID, DateTime p_dtStartTimeUTC, DateTime p_dtStopTimeUTC)
        {
            List<WiELocationDataObject> locationHistory = null;

            // Build the querystring
            string queryString = @"from e in entities where (e.Kind == """ + LOCATION_KIND + @""") && (e[""" +
                                        WiELocationDataObject.KEY_DATECOLLECTED + @"""] >= DateTime(""" + p_dtStartTimeUTC.ToString() + @""")) && (e[""" +
                                        WiELocationDataObject.KEY_DATECOLLECTED + @"""] <= DateTime(""" + p_dtStopTimeUTC.ToString() + @""")) && (e[""" +
                                        WiELocationDataObject.KEY_DEVICEID + @"""] == """ + p_guidDeviceID.ToString() + @""") select e";

            // call the helper method to query and sort the results
            locationHistory = QueryForLocationHistory(queryString, WiELocationDataObject.CompareDateCollectedForDescendingOrder);

            // return the results
            return (locationHistory);
        }

        /// <summary>
        /// Returns list of all the location data objects for the specified device that occured before p_dtEndTimeUTC
        /// </summary>
        /// <param name="p_guidDeviceID"></param>
        /// <param name="p_dtEndTime"></param>
        /// <returns></returns>
        public List<WiELocationDataObject> GetLocationHistoryForDeviceBefore(Guid p_guidDeviceID, DateTime p_dtEndTimeUTC)
        {
            List<WiELocationDataObject> locationHistory = null;

            // Build the querystring
            string queryString = @"from e in entities where (e.Kind == """ + LOCATION_KIND + @""") && (e[""" +
                                        WiELocationDataObject.KEY_DATECOLLECTED + @"""] <= DateTime(""" + p_dtEndTimeUTC.ToString() + @""")) && (e[""" +
                                        WiELocationDataObject.KEY_DEVICEID + @"""] == """ + p_guidDeviceID.ToString() + @""") select e";

            // call the helper method to query and sort the results
            locationHistory = QueryForLocationHistory(queryString, WiELocationDataObject.CompareDateCollectedForDescendingOrder);

            // return the results
            return (locationHistory);
        }

        /// <summary>
        /// Returns the current location of the device(defined as the most recent location within the past 5 minutes).
        /// </summary> 
        /// <remarks>
        /// Unfortunately SSDS doesn't seem to currently support a order by + TOP clause, so we retrieve a chunk of data, 
        /// look for the newest.  This prevents us however from implementing a "last known location" type query.
        /// </remarks>
        /// <param name="p_guidDeviceID"></param>
        /// <returns></returns>
        public WiELocationDataObject GetCurrentLocationForDevice(Guid p_guidDeviceID)
        {
            WiELocationDataObject currentLocation = null;

            // Let's try to find record(s) for the last 5 minutes
            DateTime dateTimeNowUTC = DateTime.Now.ToUniversalTime();
            DateTime dateTime5minutesAgoUTC = dateTimeNowUTC.Subtract(new TimeSpan(0, 5, 0)).ToUniversalTime();

            // Retrieve the most recent data for the member (last 5 minutes)...   
            List<WiELocationDataObject> mostRecentLocations = GetLocationHistoryForDeviceBetween(p_guidDeviceID, dateTime5minutesAgoUTC, dateTimeNowUTC);

            if ((mostRecentLocations != null) && (mostRecentLocations.Count > 0))
            {
                // Sort the list by date time descending (most recent first)
                mostRecentLocations.Sort(WiELocationDataObject.CompareDateCollectedForDescendingOrder);

                // The first item should be the newest
                currentLocation = mostRecentLocations[0];
            }

            return (currentLocation);
        }

        /// <summary>
        /// Returns the current location (defined as the most recent location in the past 5 minutes).
        /// </summary> 
        /// <remarks>
        /// Unfortunately SSDS doesn't seem to currently support a order by + TOP clause, so we retrieve a chunk of data, 
        /// look for the newest.  This prevents us however from implementing a "last known location" type query.
        /// </remarks>
        /// <param name="p_guidMemberID"></param>
        /// <returns></returns>
        public WiELocationDataObject GetCurrentLocationForMember(Guid p_guidMemberID)
        {
            WiELocationDataObject currentLocation = null;

            // Let's try to find record(s) for the last 5 minutes
            DateTime dateTimeNowUTC = DateTime.Now.ToUniversalTime();
            DateTime dateTime5minutesAgoUTC = dateTimeNowUTC.Subtract(new TimeSpan(0,5,0)).ToUniversalTime();

            // Retrieve the most recent data for the member (last 5 minutes)...   
            List<WiELocationDataObject> mostRecentLocations = GetLocationHistoryForMemberBetween(p_guidMemberID,dateTime5minutesAgoUTC,dateTimeNowUTC);

            if ((mostRecentLocations != null) && (mostRecentLocations.Count > 0))
            {
                // Sort the list by date time descending (most recent first)
                mostRecentLocations.Sort(WiELocationDataObject.CompareDateCollectedForDescendingOrder);

                // The first item should be the newest
                currentLocation = mostRecentLocations[0];
            }

            return (currentLocation);
        }

        /// <summary>
        /// Retrieves the list of all members
        /// </summary>
        /// <returns></returns>
        public List<WiEMemberDataObject> GetMembers()
        {
            List<WiEMemberDataObject> members = null;

            // Build the querystring
            string queryString = @"from e in entities where (e.Kind == """ + MEMBER_KIND + @""") select e";

            // call the helper method to query and sort the results
            members = QueryForMembers(queryString, WiEMemberDataObject.CompareMemberNameForAscendingOrder);

            // return the results
            return (members);
        }

        /// <summary>
        /// Retrives the list of members that are friends of the member specified by p_guidMemberID
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        /// <returns></returns>
        public List<WiEMemberDataObject> GetFriendsOfMember(System.Guid p_guidMemberID)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns the MemberDataObject for the member with unique ID p_guidMemberID
        /// </summary>
        /// <param name="p_guidMemberID"></param>
        /// <returns></returns>
        public WiEMemberDataObject GetMember(Guid p_guidMemberID)
        {
            WiEMemberDataObject matchingMember = null;

            string queryString = @"from e in entities where (e.Kind == """ + MEMBER_KIND + @""") && (e[""" +
                            WiEMemberDataObject.KEY_MEMBERID + @"""] == """ + p_guidMemberID.ToString() + @""") select e";


            List<WiEDataObject> matchingMembers = GetQueryResults(queryString);

            if ((null != matchingMembers) && (matchingMembers.Count > 0))
                matchingMember = (WiEMemberDataObject)matchingMembers[0];

            // Return the results
            return (matchingMember);
        }

        /// <summary>
        /// Retrieves the member whose user name matches p_strUserName.
        /// </summary>
        /// <param name="p_strUserName"></param>
        /// <returns></returns>
        public WiEMemberDataObject GetMemberByUserName(string p_strUserName)
        {
            WiEMemberDataObject matchingMember = null;

            string queryString = @"from e in entities where (e.Kind == """ + MEMBER_KIND + @""") && (e[""" +
                            WiEMemberDataObject.KEY_USERNAME + @"""] == """ + p_strUserName.ToString() + @""") select e";


            List<WiEDataObject> matchingMembers = GetQueryResults(queryString);

            if ((null != matchingMembers) && (matchingMembers.Count > 0))
                matchingMember = (WiEMemberDataObject)matchingMembers[0];

            // Return the results
            return (matchingMember);
        }

        /// <summary>
        /// Retrieves the member whose phone number matches p_strPhoneNumber
        /// </summary>
        /// <param name="p_strPhoneNumber"></param>
        /// <returns></returns>
        public WiEMemberDataObject GetMemberByPhoneNumber(string p_strPhoneNumber)
        {
            WiEMemberDataObject matchingMember = null;

            string queryString = @"from e in entities where (e.Kind == """ + MEMBER_KIND + @""") && (e[""" +
                            WiEMemberDataObject.KEY_PHONENUMBER + @"""] == """ + p_strPhoneNumber.ToString() + @""") select e";


            List<WiEDataObject> matchingMembers = GetQueryResults(queryString);

            if ((null != matchingMembers) && (matchingMembers.Count > 0))
                matchingMember = (WiEMemberDataObject)matchingMembers[0];

            // Return the results
            return (matchingMember);
        }

        /// <summary>
        /// Retrieves the member whose e-mail matches p_strEmail
        /// </summary>
        /// <param name="p_strEmail"></param>
        /// <returns></returns>
        public WiEMemberDataObject GetMemberByEmail(string p_strEmail)
        {
            WiEMemberDataObject matchingMember = null;

            string queryString = @"from e in entities where (e.Kind == """ + MEMBER_KIND + @""") && (e[""" +
                            WiEMemberDataObject.KEY_EMAIL + @"""] == """ + p_strEmail.ToString() + @""") select e"; ;

            List<WiEDataObject> matchingMembers = GetQueryResults(queryString);

            if ((null != matchingMembers) && (matchingMembers.Count > 0))
                matchingMember = (WiEMemberDataObject)matchingMembers[0];

            return matchingMember;
        }

        public WiELocationDataObject GetLocation(Guid p_guidLocationID)
        {
            throw new NotImplementedException();
        }

        public void SaveLocation(WiELocationDataObject p_locationDataObject)
        {
            // Create a new entity object and initialize it
            Entity locationHistoryEntity = DataObjectToSSDSEntity(p_locationDataObject);
            locationHistoryEntity.Kind = LOCATION_KIND;
            locationHistoryEntity.Id = p_locationDataObject.LocationID.ToString();

            // Attempt to save the location history
            if (false == SaveEntity(locationHistoryEntity))
            {
                // We were unable to save the location to the remote data store...
                throw new Exception("Unable to save location information.");
            }
            else
            {
                // We saved the record, inform anyone that cares that we posted a new location
                if (RaiseLocationHistoryChangedEvent != null)
                    RaiseLocationHistoryChangedEvent(this, new LocationHistoryChangedArgs(p_locationDataObject));
            }
        }

        public void RemoveLocation(Guid p_guidLocationID)
        {
            Scope locationScope = new Scope();

            locationScope.AuthorityId = AUTHORITY_NAME;
            locationScope.ContainerId = CONTAINER_NAME;
            locationScope.EntityId = p_guidLocationID.ToString();

            try
            {
                m_sitkaSoapService.Delete(locationScope);
            }
            catch(Exception ex)
            {
                throw new Exception("Unable to remove the location information: " + ex.ToString());
            }
        }

        public void SaveMember(WiEMemberDataObject p_memberDataObject)
        {
            // Create a new entity object and initialize it
            Entity memberEntity = DataObjectToSSDSEntity(p_memberDataObject);
            memberEntity.Kind = MEMBER_KIND;
            memberEntity.Id = p_memberDataObject.MemberID.ToString();

            // Attempt to save the location history
            if (false == SaveEntity(memberEntity))
            {
                // We were unable to save the location to the remote data store...
                throw new WiEModelException("SaveMember() - Unable to save member information for member:" + p_memberDataObject.MemberID.ToString());
            }
            else
            {
                // We saved the record, inform anyone that cares that we posted a new member
                if (RaiseMemberChangedEvent != null)
                    RaiseMemberChangedEvent(this, new MemberChangedArgs(p_memberDataObject));
            }
        }

        public void RemoveMember(Guid p_guidMemberID)
        {
            Scope memberScope = new Scope();

            memberScope.AuthorityId = AUTHORITY_NAME;
            memberScope.ContainerId = CONTAINER_NAME;
            memberScope.EntityId = p_guidMemberID.ToString();

            try
            {
                m_sitkaSoapService.Delete(memberScope);
            }
            catch (Exception ex)
            {
                throw new Exception("Unable to remove the member information: " + ex.ToString());
            }
        }

        public void SaveDevice(WiEDeviceDataObject p_deviceDataObject)
        {
            // Create a new entity object and initialize it
            Entity deviceEntity = DataObjectToSSDSEntity(p_deviceDataObject);
            deviceEntity.Kind = DEVICE_KIND;
            deviceEntity.Id = p_deviceDataObject.DeviceID.ToString();

            // Attempt to save the location history
            if (false == SaveEntity(deviceEntity))
            {
                // We were unable to save the device to the remote data store...
                throw new WiEModelException("SaveDevice() - Unable to save device information to the SSDS data store.");
            }
            else
            {
                // We saved the record, inform anyone that cares that we posted a new device
                if (RaiseDeviceChangedEvent != null)
                    RaiseDeviceChangedEvent(this, new DeviceChangedArgs(p_deviceDataObject));
            }
        }

        public WiEDeviceDataObject GetDevice(Guid p_guidDeviceID)
        {
            WiEDeviceDataObject matchingDevice = null;

            string queryString = @"from e in entities where (e.Kind == """ + DEVICE_KIND + @""") && (e[""" +
                            WiEDeviceDataObject.KEY_DEVICEID + @"""] == """ + p_guidDeviceID.ToString() + @""") select e";

            List<WiEDataObject> matchingDevices = GetQueryResults(queryString);

            if ((null != matchingDevices) && (matchingDevices.Count > 0))
                matchingDevice = (WiEDeviceDataObject)matchingDevices[0];

            return (matchingDevice);
        }

        public List<WiEDeviceDataObject> GetDevicesByMember(Guid p_guidMemberID)
        {
            List<WiEDeviceDataObject> matchingDevices = null;

            string queryString = @"from e in entities where (e.Kind == """ + DEVICE_KIND + @""") && (e[""" +
                            WiEDeviceDataObject.KEY_MEMBERID + @"""] == """ + p_guidMemberID.ToString() + @""") select e";

            // Attempt to query for any matching devices (returns a generic list of DataObjects)
            List<WiEDataObject> matchingDataObjects = GetQueryResults(queryString);
            if ((null != matchingDevices) && (matchingDevices.Count > 0))
            {
                // A bit inefficient (need to rework this) to populate a typed list...  
                // May be ok since typically it is a list of 1 item.
                foreach (WiEDataObject dataObject in matchingDataObjects)
                    matchingDevices.Add((WiEDeviceDataObject)dataObject);
            }

            // return the results
            return (matchingDevices);
        }



        #endregion
    }
}
